查看原文
其他

做完小程序项目、老板给我加了6k薪资~

苏南 画漫画的程序员 2022-06-11


  大家好,这里是@IT·平头哥联盟,我是 首席填坑官——苏南(South·Su),今天要给大家分享的是最近公司做的一个小程序项目,过程中的一些好的总结和遇到的坑,希望能给其他攻城狮带来些许便利,更希望能像标题所说,做完老板给你加薪~

  今天是中秋节的第一天,假日的清晨莫名的醒的特别早,不知道为什么,也许是因为,昨晚公司上线的项目回来的路上,发现了个小bug,心里有些忐忑吧,一会偷偷先改了,让领导发现这个月绩效就没了~~~~

  以上纯为扯淡,现在开始一本正经的装逼,请系好安全带,中间过程有可能会开车,请注意安全!!!!!

  最近这个项目跟团队小伙伴沟通在众多框架中最后选择了 wepy,没有直接用原生的,小程序原生就……,大家都懂的,用 wepy框架,给自己带来了便利,也带来了不少坑,但纵是如此,我还是怀着:“纵你虐我千百遍,我仍待你如初恋”的心态去认真把项目做好。

toast组件

  • toast组件,大家都知道,官方的api wx.showToast 是满足不了我们的需求的,因为它只支持 "success", "loading"两种状态,同时“ title 文本最多显示 7 个汉字长度”,这是官方原话,有图有真相哦,样式巨丑~

  1. wx.showToast({

  2.  title: '成功',

  3.  icon: 'success',

  4.  duration: 2000

  5. })

  6. wx.showModal({

  7.  title: '提示',

  8.  content: '首席填坑官∙苏南帅不帅?',

  9.  success: function(res) {

  10.    if (res.confirm) {

  11.      console.log('用户点击确定,表示很帅')

  12.    } else if (res.cancel) {

  13.      console.log('用户点击取消')

  14.    }

  15.  }

  16. })

wx.showModal的content的文字是不会居中的(现在不确定有没有扩展,可以设置),依稀记得有一次因为问题差点跟产品经理吵起来,让文字居中,我说最少要两小时,当时产品就炸了,什么鬼???让文字居中一下要两小时??两小时??两小时??呵呵~走了,后来就下决定自己封装了一个属于自己的toast组件,以下为部分核心代码:

  1. <template lang="wxml">

  2.    <view class="ui-toast  {{ className }}" hidden="{{ !visible }}">

  3.        <view class="ui-toast_bd">

  4.            <icon wx:if="{{ options.icon}}" type="{{ options.icon }}" size="40" color="{{ options.color }}" class="ui-toast_icon" />

  5.            <view class="ui-toast_text">{{ options.text }}</view>

  6.        </view>

  7.    </view>

  8. </template>

  9. <script>

  10.    import wepy from 'wepy';

  11.    const __timer__ =1900;

  12.    //方法以 : __XX__ 命名,因并入其他组件中后,可能引起方法名重复

  13.    class Toast extends wepy.component {

  14.        /**

  15.         * 默认数据

  16.         */

  17.        data={

  18.             list:[

  19.                {

  20.                    type: `success`,

  21.                    icon: `success`,

  22.                    className: `ui-toast-success`,

  23.                },

  24.                {

  25.                    type: `cancel`,

  26.                    icon: `cancel`,

  27.                    className: `ui-toast-cancel`,

  28.                },

  29.                {

  30.                    type: `forbidden`,

  31.                    icon: `warn`,

  32.                    className: `ui-toast-forbidden`,

  33.                },

  34.                {

  35.                    type: `text`,

  36.                    icon: ``,

  37.                    className: `ui-toast-text`,

  38.                },

  39.            ],

  40.            timer:null,

  41.            scope: `$ui.toast`,

  42.            animateCss:'animateCss',

  43.            className:'',

  44.            visible:!1,

  45.            options:{

  46.                type: ``,

  47.                timer: __timer__,

  48.                color: `#fff`,

  49.                text: `已完成`,

  50.            }

  51.        }

  52.        /**

  53.         * 默认参数

  54.         */

  55.        __setDefaults__() {

  56.            return {

  57.                type: `success`,

  58.                timer: __timer__,

  59.                color: `#fff`,

  60.                text: `已完成`,

  61.                success() {},

  62.            }

  63.        }

  64.        /**

  65.         * 设置元素显示

  66.         */

  67.        __setVisible__(className = `ui-animate-fade-in`) {

  68.            this.className = `${this.animateCss} ${className}`;

  69.            this.visible = !0;

  70.            this.$apply();

  71.        }

  72.        /**

  73.         * 设置元素隐藏

  74.         */

  75.        __setHidden__(className = `ui-animate-fade-out`, timer = 300) {

  76.            this.className = `${this.animateCss} ${className}`;

  77.            this.$apply();

  78.            setTimeout(() => {

  79.                this.visible = !1;

  80.                this.$apply();

  81.            }, timer)

  82.        }

  83.        /**

  84.         * 显示toast组件

  85.         * @param {Object} opts 配置项

  86.         * @param {String} opts.type 提示类型

  87.         * @param {Number} opts.timer 提示延迟时间

  88.         * @param {String} opts.color 图标颜色

  89.         * @param {String} opts.text 提示文本

  90.         * @param {Function} opts.success 关闭后的回调函数

  91.         */

  92.        __show__(opts = {}) {

  93.            let options = Object.assign({}, this.__setDefaults__(), opts)

  94.            const TOAST_TYPES = this.list;

  95.            TOAST_TYPES.forEach((value, key) => {

  96.                if (value.type === opts.type) {

  97.                    options.icon = value.icon;

  98.                    options.className = value.className

  99.                }

  100.            })

  101.            this.options = options;

  102.            if(!this.options.text){

  103.                return ;

  104.            };

  105.            clearTimeout(this.timer);

  106.            this.__setVisible__();

  107.            this.$apply();

  108.            this.timer = setTimeout(() => {

  109.                this.__setHidden__()

  110.                options.success&&options.success();

  111.            }, options.timer);

  112.        }

  113.        __info__(args=[]){

  114.            let [ message, callback, duration ]  = args;

  115.            this.__show__({

  116.                type: 'text',

  117.                timer: (duration||__timer__),

  118.                color: '#fff',

  119.                text: message,

  120.                success: () => {callback&&callback()}

  121.            });

  122.        }

  123.        __success__(args=[]){

  124.            let [ message, callback, duration ]  = args;

  125.            this.__show__({

  126.                type: 'success',

  127.                timer: (duration||__timer__),

  128.                color: '#fff',

  129.                text: message,

  130.                success: () => {callback&&callback()}

  131.            });

  132.        }

  133.        __warning__(args){

  134.            let [ message, callback, duration ]  = args;

  135.            this.__show__({

  136.                type: 'forbidden',

  137.                timer: (duration||__timer__),

  138.                color: '#fff',

  139.                text: message,

  140.                success: () => {callback&&callback()}

  141.            });

  142.        }

  143.        __error__(args){

  144.            let [ message, callback, duration ]  = args;

  145.            this.__show__({

  146.                type: 'cancel',

  147.                timer: (duration||__timer__),

  148.                color: '#fff',

  149.                text: message,

  150.                success: () => {callback&&callback()}

  151.            });

  152.        }

  153.        __showLoading__(options){

  154.            wx.showLoading({

  155.                title: (options&&options.title||"加载中"),

  156.            });

  157.        }

  158.        __hideLoading__(){

  159.            wx.hideLoading();

  160.        }

  161.        onLoad(){

  162.            this.$apply()

  163.        }

  164.    }

  165.    export default Toast;

  166. </script>

调用示例:

  1. <template>

  2.    <view class="demo-page">

  3.        <Toast />

  4.        <Modals />

  5.    </view>

  6. </template>

  7. <script>

  8.    import wepy from 'wepy'

  9.    import Toast from '../components/ui/Toast'

  10.    import Modals from '../components/ui/Modals'

  11.    import {fetchJson} from '../utils/fetch';

  12.    export default class Index extends wepy.page {

  13.        config = {

  14.            navigationBarBackgroundColor: "#0ECE8D",

  15.      navigationBarTextStyle:"white",

  16.            navigationBarTitleText: ''

  17.        }

  18.        components = {

  19.            Toast: Toast,

  20.            Modals: Modals

  21.        }

  22.        methods = {

  23.            tapToast(){

  24.                this.$invoke("Toast","__success__",[`本文由@IT·平头哥联盟-首席填坑官∙苏南分享`]);

  25.            }  

  26.        }

  27.    }

  28. </script>

Storage (数据存储)

Storage (存储)在前端我们存储的方式, cookie、 localStorage、 sessionStorage等这些,特性就不一一说明了,小程序里大家都知道,数据存储只能调用 wx.setStorage、wx.setStorageSync,相当于h5的 localStorage,而 localStorage是不会过期的,这个大家都知道,而且在很多的面试中,面试官都会问到这个问题,怎么让localStorage像 cookie一样,只存两小时、两天、甚至只存两分钟呢?今天带你解惑,让你在职场面试中又减少一个难题,这也是我们项目中一直在用的方式,小程序中也同样实用:

  1. class storage {

  2.  constructor(props) {

  3.    this.props = props || {}

  4.    this.source =  wx||this.props.source;

  5.  }

  6.  get(key) {

  7.    const data = this.source,

  8.          timeout = (data.getStorageSync(`${key}__expires__`)||0)

  9.    // 过期失效

  10.    if (Date.now() >= timeout) {

  11.      this.remove(key)

  12.      return;

  13.    }

  14.    const value = data.getStorageSync(key)

  15.    return value

  16.  }

  17.  // 设置缓存

  18.  // timeout:过期时间(分钟)

  19.  set(key, value, timeout) {

  20.    let data = this.source

  21.    let _timeout = timeout||120;

  22.    data.setStorageSync(key,(value));

  23.    data.setStorageSync(`${key}__expires__`,(Date.now() + 1000*60*_timeout));

  24.    return value;

  25.  }

  26.  remove(key) {

  27.    let data = this.source

  28.        data.removeStorageSync(key)

  29.        data.removeStorageSync(`${key}__expires__`)

  30.    return undefined;

  31.  }

  32. }

  33. module.exports = new storage();

  其实很简单,大家看了之后就都 “哦,原来还可以这样” 懂了,只是一时没想到而已,就是个小技巧,每次在存储的时候同时也存入一个时效时间戳,而在获取数据前,先与当前时间比较,如果小于当前时间则过期了,直接返回空的数据。

接口API维护

  • 接口API的维护,在没有 nodejs之前,前端好像一直都在为处理不同环境(dev、test、uat、prd)下调用对应的API而烦恼,做的更多的就是用域名来进行判断,当然也有些高级一点的做法,后端在页面渲染的时候,存一个变量到 cookie里或者在页面输出一个全局的api变量(建立在没有前后端分离的基础上),到了小程序同样也是如此,每次都要手动改环境,那么一个项目可能有不同的业务,要调用不同域名api,又有不同的环境区分,怎么维护会比较好呢??

  1. env/dev.js

  2. //本地环境

  3. module.exports = {

  4.    wabApi:{

  5.        host:"https://dev-ali.southsu.com/XX/api/**",

  6.    },

  7.    questionApi:{

  8.        host:"https://dev-ali.bin.com/question/api/**/question",

  9.    },

  10.    mockApi:{

  11.        host:"https://easy.com/mock/594635**c/miniPrograms"

  12.    },

  13.    inWelApi: {

  14.        host: "https://dev.**.com/Wab/api/escene/v2"  

  15.    }

  16. };

  1. import dev from './env/dev'; //本地或开发

  2. import uat from './env/pre'; //体验环境

  3. import prd from './env/prd'; //线上

  4. var ENV = "prd"; //'dev | uat | prd';

  5. let _base_ = {

  6.  dev,

  7.  uat,

  8.  prd

  9. }[ENV];

  10. var config = {

  11.  ENV,

  12.  baseAPI:{..._base_, env : ENV },

  13.  appID:"wx*****b625e", //公司账号(指数)appid

  14.  isAuthorization:true,

  15.  'logId': 'gVDSMH****HAas4qSSOTb-gzGzoHsz',

  16.  'logKey': 'pxFOg****Jn3JyjOVr',

  17.  questionnaireNo:'z**Insu' // 问卷调查编号

  18. };

  19. export const __DEBUG__ = (ENV!="prd");

  20. export default  config;

  1. 请求调用api处理的示例

  2. import wepy from 'wepy'

  3. import _login_ from './login';

  4. import config,{__DEBUG__} from './config';

  5. import 'wepy-async-function';

  6. export const  fetchJson = (options)=>{

  7.    /*

  8.     *  请求前的公共数据处理

  9.     * @ param {String}     url 请求的地址

  10.     * @ param {String}     Type 请求类型

  11.     * @ param {String}     sessionId 用户userToken

  12.     * @ param {Boolean}    openLoad 开启加载提示,默认开启,true-开,false-关

  13.     * @ param {function} StaticToast 静态提示方法 ,详细说明请参考 components/ui/Toast

  14.     * @ param {Object}     header 重置请求头

  15.     * @ param {Boolean}    isMandatory 是否强制用户授权,获取用户信息

  16.    */

  17.    StaticToast = getCurrentPages()[getCurrentPages().length - 1];

  18.    let { url,openLoad=true, type, data={},header={}, ...others } = options||{};

  19.    let sessionId = (Storage.get(__login__.server+'_userToken')||"");

  20.    /*Start */

  21.        var regExp = /\/(.*?)\//,

  22.        hostkey = url.match(regExp)[1];

  23.    let baseUrl = config.baseAPI[hostkey].host;

  24.    url = url.replace(regExp, '/');

  25.    /*End */

  26.    __DEBUG__&&console.log('#--baseUrl:', baseUrl);

  27.    __DEBUG__&&console.log('#--请求地址:', `${baseUrl}${url}`);

  28.    __DEBUG__&&console.log('----------分割线---------------');

  29.    openLoad&&StaticToast.__showLoading__();

  30.    return new Promise((resolve, reject) => {

  31.        return wepy.request({

  32.            url:`${baseUrl}${url}`,

  33.            method:(type || 'POST'),

  34.            data,

  35.            header:{

  36.                "t9oken":sessionId,

  37.                'content-type': 'application/json',

  38.                // 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',

  39.                ...header

  40.            },

  41.            success:(res)=>{

  42.                StaticToast.__hideLoading__();

  43.                return resolve(resHandler(res,options));

  44.            },

  45.            error:(err,status)=>{

  46.                StaticToast.__hideLoading__();

  47.                return reject(errorHandler(err,options,err.statusCode));

  48.            }

  49.        });

  50.    })

业务调用示例:

  1. fetchJson({

  2.    type:"post",

  3.    // url:"/mockApi/service/XXX", 最后请求得到的地址是 https://easy.com/mock/594635**c/miniPrograms/service/XXX (域名不同环境不一样,在config里的 ENV baseAPI控制)

  4.    data:{

  5.        name:"苏南"

  6.    },

  7.    success:res=>{

  8.        console.log("大家好,我是@IT·平头哥联盟-首席填坑官∙苏南",res)

  9.    }

  10. })

填坑时间了

填坑时间了, wepy框架中每个组件内的生命周期回调 onload,只要是引入了组件,不管你视图有没有渲染,他都会执行,导致某些业务逻辑用不上它的时候也执行了产生异常(当然为个锅< 小程序 >肯定说我不背~^~ ),详细看链接:https://github.com/Tencent/wepy/issues/975 ,https://github.com/Tencent/wepy/issues/1386 ,不知道后面有没有人解决。

rich-text组件

  • rich-text,小程序的一个组件,虽然有那么一点点用处,但又不得不说到底要它何用啊?其它的我就忍了, a标签, a标签啊,属性没有,那还要它何用啊??你都不要我跳转,我还要用你吗?b、i、span、em……哪个我不能用?不知道设计这个组件的人是不是脑被驴踢了(愿老天保佑,我在这骂他,可千万别被看到了,哈哈~),又是业务需求后台配置的内容有链接,没办法,来吧,搞吧,往死里搞吧,一切的推脱都是你技术low的借口(你看,你看,别人的怎么可以跳转啊,别人怎么做到的?给我一刀,我能把产品砍成渣),所以有了后面的填坑:

  1. <template>

  2.    <view class="test-page">

  3.            <button @tap="cutting">点击解析html</button>

  4.            <view wx:if="{{result.length>0}}" class="parse-list">

  5.                    <view  class="parse-view" wx:for="{{result}}" wx:key="unique" wx:for-index="index" wx:for-item="items">

  6.                            <block wx:if="{{items.children&&items.children.length}}">

  7.                                    <block wx:for="{{items.children}}" wx:for-item="child" >

  8.                                            <text wx:if="{{child.type == 'link'}}" class="parse-link" @tap="goToTap({{child.link}})">{{child.value}}</text>

  9.                                            <text class="parse-text" wx:else>{{child.value}}</text>

  10.                                    </block>

  11.                            </block>

  12.                            <text class="parse-text" wx:else>{{items.value}}</text>

  13.                    </view>

  14.            </view>

  15.            <Toast />

  16.            <Modals />

  17.    </view>

  18. </template>

  19. <script>

  20.    import wepy from 'wepy'

  21.    import { connect } from 'wepy-redux'

  22.    import Toast from '../components/ui/Toast'

  23.    import Modals from '../components/ui/Modals'

  24.    import {fetchJson} from '../utils/fetch';

  25.    import Storage from "../utils/storage";

  26.    function wxHtmlParse(htmlStr=''){

  27.        if(!htmlStr){

  28.                return []

  29.        };

  30.        const httpExp  =/(http:\/\/|https:\/\/)((\w|=|\?|\.|\/|\&|-)+)/g;//提取网址正则

  31.        const aExp=/<a.[^>]*?>([(^<a|\s\S)]*?)<\/a>/ig; //a标签分割正则

  32.        let cuttingArr = htmlStr.split(/[\n]/);

  33.        let result = [];

  34.        //有a标签的html处理

  35.        let itemParse = (itemHtml='')=>{

  36.                let itemCutting = itemHtml.split(aExp)||[];

  37.                let itemResult = [];

  38.                for(var i = 0;i<itemCutting.length;i++){

  39.                        let _html = itemCutting[i];

  40.                        if(_html!==''){

  41.                                let itemData = {

  42.                                        value:_html,

  43.                                        type:'text',

  44.                                        class:"parse-text"

  45.                                };

  46.                                let matchTag = itemHtml.match(aExp)||[]; //再次匹配有 a 标签的

  47.                                if(matchTag.length){

  48.                                        let itemIndex = matchTag.findIndex((k,v)=>(k.indexOf(_html)!==-1));

  49.                                        if(itemIndex>=0){

  50.                                                let link = matchTag[itemIndex].match(httpExp)[0];

  51.                                                itemData.type = 'link';

  52.                                                itemData.link = link;

  53.                                                itemData.class = "parse-link";

  54.                                        };

  55.                                };

  56.                                itemResult.push(itemData)

  57.                        }

  58.                };

  59.                return itemResult;

  60.        };

  61.        cuttingArr.map((k,v)=>{

  62.                let itemData = {type : "view",class:"parse-view"};

  63.                let isATag = k.match(aExp);

  64.                if(isATag){

  65.                        itemData.children = itemParse(k);

  66.                }else{

  67.                        itemData.value = k;

  68.                };

  69.                result.push(itemData);

  70.                return k;

  71.        }) ;

  72.        return result;

  73.    };

  74.    export default class Index extends wepy.page {

  75.        config = {

  76.            navigationBarBackgroundColor: "#0ECE8D",

  77.            navigationBarTextStyle:"white",

  78.                navigationBarTitleText: '小程序解析数据中的a标签'

  79.        }

  80.        components = {

  81.            Toast: Toast,

  82.            Modals: Modals

  83.        }

  84.        data = {

  85.            html:'大家好,我是苏南(South·Su),\n职业:@IT·平头哥联盟-首席填坑官,\n身高:176cm,\n性别:男,\n性取向:女,\n公司:目前就职于由腾讯、阿里、平安三巨头合资的一家互联网金融公司深圳分公司某事业部、,\n简介:宝剑锋从磨砺出 梅花香自苦寒来,认真做自己,乐于分享,希望能尽绵薄之力 助其他同学少走一些弯路!,gitHub:https://github.com/meibin08/,\n兴趣:跑步、羽毛球、爬山、音乐、看书、分享自己的微薄知识帮助他人……,\n其他:想了解更多吗?可以加入<a href="https://honeybadger8.github.io/blog/#/">386485473交流群</a>,也可以给我电话<a href="https://github.com/meibin08/">134XX852xx5</a> ,开玩笑啦',

  86.            result:[]

  87.        }

  88.        methods = {

  89.            cutting(e){

  90.                this.result = wxHtmlParse(this.html);

  91.                console.log(`result`,this.result);

  92.                this.$apply();

  93.            },

  94.        }

  95.    }

  96. </script>


PS完整示例源码 在公众号回复“前端”即可获取哦

今天的分享就到这里,写了蛮久,最近才在开始尝试写博客,新手上路中,文章中有不对之处,烦请各位大神斧正。如果你觉得这篇文章对你有所帮助,请记得点赞哦~,想了解更多,就请持续关注我们吧!


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存